package net.seabookchen.dao;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map;

import javax.persistence.Embedded;
import javax.persistence.Transient;

import com.google.appengine.api.datastore.EntityNotFoundException;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.Query;
import com.googlecode.objectify.util.DAOBase;

public class ObjectifyGenericDao<T> extends DAOBase {
	private static final int BAD_MODIFIERS = Modifier.FINAL | Modifier.STATIC | Modifier.TRANSIENT;
	private Class<T> clazz;

	public ObjectifyGenericDao(Class<T> clazz) {
		this.clazz = clazz;
	}

	/**
	 * Add or update the exisiting entity.
	 * 
	 * @param entity
	 * @return
	 */
	public Key<T> put(T entity) {
		Key<T> key = ofy().put(entity);
		return key;
	}

	/**
	 * Add or update a batch entities
	 * 
	 * @param entity
	 * @return
	 */
	public Map<Key<T>, T> putAll(T... entities) {
		Map<Key<T>, T> keys = ofy().put(entities);
		return keys;
	}

	/**
	 * Add or update a batch entities
	 * 
	 * @param entity
	 * @return
	 */
	public Map<Key<T>, T> putAll(Iterable<T> entities) {
		Map<Key<T>, T> keys = ofy().put(entities);
		return keys;
	}

	/**
	 * delete an entity
	 * 
	 * @param entity
	 * @return
	 */
	public void delete(T entity) {
		ofy().delete(entity);
	}
	
	public void delete(Long id) {
		ofy().delete(this.clazz,id);
	}

	/**
	 * delete all the entities
	 * 
	 * @param entity
	 * @return
	 */
	public void deleteAll(T... entities) {
		ofy().delete(entities);
	}

	/**
	 * delete all the entities
	 * 
	 * @param entity
	 * @return
	 */
	public void deleteAll(Iterable<T> entities) {
		ofy().delete(entities);
	}

	/**
	 * delete an entity by Key<T>
	 * 
	 * @param entityKey
	 */
	public void delete(Key<T> entityKey) {
		ofy().delete(entityKey);
	}

	/**
	 * Get an Entity
	 * 
	 * @param id
	 * @return
	 * @throws EntityNotFoundException
	 */
	public T get(Long id) throws EntityNotFoundException {
		T obj = ofy().get(this.clazz, id);
		return obj;
	}

	public T get(Key<T> id) throws EntityNotFoundException {
		T obj = ofy().get(id);
		return obj;
	}

	/**
	 * Find more than one Entity by the propValue
	 * 
	 * @param propName
	 * @param propValue
	 * @return
	 */
	public List<T> getEntitiesByProperty(String propName, Object propValue) {
		Query<T> q = ofy().query(clazz);
		q.filter(propName, propValue);
		return q.list();
	}

	/**
	 * Find a single Entity by the propValue
	 * 
	 * @param propName
	 * @param propValue
	 * @return
	 */
	public T getEntityByProperty(String propName, Object propValue) {
		Query<T> q = ofy().query(clazz);
		q.filter(propName, propValue);
		return q.get();
	}

	/**
	 * Find more than one Entities
	 * 
	 * @param propName
	 * @param propValue
	 * @return
	 */
	public List<Key<T>> getKeysByProperty(String propName, Object propValue) {
		Query<T> q = ofy().query(clazz);
		q.filter(propName, propValue);
		return q.listKeys();
	}

	/**
	 * Find more than one Entities by more than one properties
	 * @param criteriaEntity -- this is for containing all the properties
	 * @return
	 */
	public List<T> getEntitiesByMuliProperties(T criteriaEntity) {
		Query<T> q = buildQuerys(criteriaEntity);
		return q.list();
	}
	
	/**
	 * Find one Entity.
	 * @param criteriaEntity -- this is for containing all the properties
	 * @return
	 */
	public T getEntityByMuliProperties(T criteriaEntity) {
		Query<T> q = buildQuerys(criteriaEntity);
		return q.get();
	}

	private Query<T> buildQuerys(T criteriaEntity) {
		Query<T> q = ofy().query(clazz);
		
		for (Field field : clazz.getDeclaredFields()) {
			if ((field.isAnnotationPresent(Transient.class)) 
					|| (field.isAnnotationPresent(Embedded.class))
					|| (field.getType().isArray())
					|| (Collection.class.isAssignableFrom(field.getType())
					|| ((field.getModifiers() & BAD_MODIFIERS) != 0))
				) {
				continue;
			}
			
			field.setAccessible(true);
			Object value = null;
			try {
				value = field.get(criteriaEntity);
				if (value != null) {
					q.filter(field.getName(), value);
				}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}
		return q;
	}
	
	public List<T> deadOrLive(Query<T> query) {
		return query != null? query.list(): null;
	}
}
